/*
 * Copyright (C) 2015-present, Wei Chou (weichou2010@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wei.c.widget;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.Adapter;
import android.widget.FrameLayout;

import com.wei.c.widget.SlideEventTracker.OnRequestInvalidationListener;
import com.wei.c.widget.SlideEventTracker.OnScrollListener;
import com.wei.c.widget.SlideEventTracker.OnSlideListener;
import com.wei.c.widget.SlideEventTracker.Result;

/**
 * 可竖直拖动移除层叠卡片的View, 用法类似于ListView.
 * {@link AbsVSlideAdapter#getView(int, View, ViewGroup)}的第一个参数position与子View的index顺序相反，即：
 * 沿着index从大到小的方向上，position从小到大变化。
 * {@link #getSelectedView()}表示当前正在显示的View, index最大，position最小。
 * 
 * 未使用AdapterView<VSlideAdapter>是因为不能addView().
 * 
 * @author 周伟 Wei Chou(weichou2010@gmail.com)
 */
public class VSlideView extends FrameLayout implements OnRequestInvalidationListener {
    private static final boolean DEBUG          = false;
    private static final boolean DEBUGx         = false;
    private static final String TAG             = VSlideView.class.getSimpleName();

    private static final int ANIM_DURATION          = 350;
    private static final int ACTIVE_COUNT           = 4;
    private static final float STICKY_FIRST_LAST    = 0.1f;

    private final RecycleBin mViewScraps = new RecycleBin();
    private SlideEventTracker mSEvTracker;
    private AbsVSlideAdapter<?> mAdapter;
    private OnSelectionListener mOnSelectionListener;
    private boolean mRegisted;
    private boolean mActiveViewsUpdated;
    private int mCurrPos, mPrevPos;
    private int mItemCount;
    private ScrollableFrameLayout mCurrView, mSlideOutView;
    private View mEmptyView;

    private int mAnimDuration       = ANIM_DURATION;
    private float mStickyFirstLast  = STICKY_FIRST_LAST;
    private int mHeight, mDp48;
    private int mCurrScrollByY;
    private int mCurrViewDownScrollY, mSlideOutViewDownScrollY;
    private boolean mScrollCalled;
    private boolean mSliding;
    private boolean mStartSlideAtFirst, mStartSlideAtLast;
    private boolean mSlideOutDirectionUp = true;

    public VSlideView(Context context) {
        super(context);
        init();
    }

    public VSlideView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VSlideView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mDp48 = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics());
        mSEvTracker = new SlideEventTracker(this, 30, 1, 1000,
                AnimationUtils.loadInterpolator(getContext(), android.R.anim.decelerate_interpolator),
                mOnSlideListener, mOnScrollListener, false);
    }

    private OnSlideListener mOnSlideListener = new OnSlideListener() {
        private boolean animInterrupted;

        @Override
        public void onSlideEventBeginX(MotionEvent ev, boolean prevAnimInterrupted) {
            if (DEBUG) Log.d(TAG, "onSlideEvent++++BeginX++++prevAnimInterrupted:" + prevAnimInterrupted);
        }

        @Override
        public void onSlideEventBeginY(MotionEvent ev, boolean prevAnimInterrupted) {
            if (DEBUG) Log.d(TAG, "onSlideEvent----BeginY----prevAnimInterrupted:" + prevAnimInterrupted);
            if (mActiveViewsUpdated) {
                if (DEBUG) Log.w(TAG, "onSlideEvent----BeginY----1");
                animInterrupted = prevAnimInterrupted;
                if (mSlideOutDirectionUp) {
                    mSlideOutViewDownScrollY = mSlideOutView != null ? Math.min(mSlideOutView.getScrollY(), mHeight) : mHeight;
                    mCurrViewDownScrollY = mCurrView != null ? Math.max(mCurrView.getScrollY(), 0) : 0;
                } else {
                    mSlideOutViewDownScrollY = mSlideOutView != null ? Math.max(mSlideOutView.getScrollY(), -mHeight) : -mHeight;
                    mCurrViewDownScrollY = mCurrView != null ? Math.min(mCurrView.getScrollY(), 0) : 0;
                }
                mCurrScrollByY = 0;
                mStartSlideAtFirst = mCurrPos == 0;
                mStartSlideAtLast = (mCurrPos == mItemCount - 1);
                mSliding = true;
            }
        }

        @Override
        public void onSlideEventMoveX(boolean haveSlideIntention, int scrollBy) {}

        @Override
        public void onSlideEventMoveY(boolean haveSlideIntention, int scrollBy) {
            if (DEBUG) Log.w(TAG, "onSlideEvent----MoveY----haveSlideIntention:" + haveSlideIntention + ", scrollBy:" + scrollBy);
            if (mActiveViewsUpdated && (animInterrupted || haveSlideIntention)) {
                if (DEBUG) Log.w(TAG, "onSlideEvent----MoveY----1");
                if (mCurrView.isEventEnabled()) mCurrView.setEventEnabled(false);
                mSEvTracker.smoothScrollY(mCurrScrollByY, scrollBy, VSlideView.this);
            }
        }

        @Override
        public void onSlideEventEndX(boolean haveSlideIntention, boolean prevAnimInterrupted, float velocity, boolean sysDefFling) {}

        @Override
        public void onSlideEventEndY(boolean haveSlideIntention, boolean prevAnimInterrupted, float velocity, boolean sysDefFling) {
            if (DEBUG) Log.e(TAG, "onSlideEvent----EndY----velocity:" + velocity + ", sysDefFling:" + sysDefFling);
            /**********本方法里不进行setSelection()切换**********/
            if (mActiveViewsUpdated) {
                if (DEBUG) Log.e(TAG, "onSlideEvent----EndY----1");
                final int target;
                if (mSlideOutView != null) {
                    //呼应int outViewScrollY = mSlideOutViewDownScrollY - target;
                    target = mSlideOutViewDownScrollY - getTargetScrollY(mSlideOutView, mCurrScrollByY < 0, velocity, sysDefFling);
                } else {
                    target = mCurrViewDownScrollY - (mCurrPos == mItemCount - 1 ? 0 : getTargetScrollY(mCurrView, mCurrScrollByY < 0, velocity, sysDefFling));
                }
                if (target == mCurrScrollByY) {
                    mSEvTracker.abortScrollY();
                    mOnScrollListener.onScrollFinishedY();
                } else {
                    mSEvTracker.startScrollY(mCurrScrollByY, target, mAnimDuration, VSlideView.this);
                    /* 苟且的办法，解决某些手机在本父View没有scrollY的情况下，invalidate不导致重绘的问题。
                     * 虽然mCurrScrollByY.invalidate()可以触发，但是由于频繁add/remove操作导致displayList崩溃的问题。*/
                    setScrollY(1);
                }
            }
        }
    };

    private final OnScrollListener mOnScrollListener = new OnScrollListener() {
        private boolean beforeFirst, afterLast;

        @Override
        public void onScrollToX(int target, float velocity, boolean touching) {}

        @Override
        public void onScrollToY(int target, float velocity, boolean touching) {
            if (DEBUG) Log.i(TAG, "onScrollToY----target:" + target + ", velocity:" + velocity);
            /**********这里会进行setSelection()切换**********/
            mCurrScrollByY = target;
            if (mSlideOutDirectionUp) {
                if (mSlideOutView != null) {
                    int outViewScrollY = mSlideOutViewDownScrollY - target;
                    if (DEBUG) Log.d(TAG, "onScrollToY----outViewScrollY:" + outViewScrollY);
                    if (outViewScrollY <= mHeight) {
                        if (afterLast && touching && mCurrView.getScrollY() <= 0) afterLast = false;
                        if (outViewScrollY >= 0) {
                            mSlideOutView.setScrollY(outViewScrollY);
                            if (mCurrView.getScrollY() != 0) mCurrView.setScrollY(0);
                        } else {
                            setSelectionInner(mCurrPos - 1, false);
                            mCurrView.setEventEnabled(false);
                            if (afterLast) afterLast = false;
                            //mSlideOutView已经改变
                            if (mSlideOutView != null) {
                                outViewScrollY += mHeight; //把新的拉下来
                                mSlideOutView.setScrollY(outViewScrollY);
                                mSlideOutViewDownScrollY = outViewScrollY + target; //呼应前面的int outViewScrollY = mSlideOutViewDownScrollY - target;
                            }
                            mCurrView.setScrollY(0);
                            mCurrViewDownScrollY = 0 + target;
                        }
                    } else {
                        if (mCurrPos == mItemCount - 1) {
                            mSlideOutView.setScrollY(mHeight);
                            outViewScrollY = (int) ((outViewScrollY - mHeight) * mStickyFirstLast);
                            mCurrView.setScrollY(outViewScrollY);
                            if (mStartSlideAtLast && !afterLast) afterLast = true;
                        } else {
                            setSelectionInner(mCurrPos + 1, false);
                            mCurrView.setEventEnabled(false);
                            outViewScrollY -= mHeight;
                            mSlideOutView.setScrollY(outViewScrollY);  //必然不为null, mCurrView变过去的
                            mCurrView.setScrollY(0);
                            mSlideOutViewDownScrollY = outViewScrollY + target;
                            mCurrViewDownScrollY = 0 + target;
                        }
                    }
                } else if (mCurrView != null) {
                    int scrollY = mCurrViewDownScrollY - target;
                    if (DEBUGx) Log.d(TAG, "onScrollToY----scrollY:" + scrollY);
                    if (mCurrPos == mItemCount - 1) {   //说明只有一个(mItemCount == 1)，因为mSlideOutView == null, 又是最最后一个
                        if (scrollY > 0) {
                            if (mStartSlideAtLast && !afterLast) afterLast = true;
                            if (beforeFirst) beforeFirst = false;
                        } else if (scrollY < 0) {
                            if (mStartSlideAtFirst && !beforeFirst) beforeFirst = true;
                            if (afterLast) afterLast = false;
                        }
                        scrollY = (int) (scrollY * mStickyFirstLast);
                        mCurrView.setScrollY(scrollY);
                    } else {
                        if (beforeFirst && touching && scrollY >= 0) beforeFirst = false;
                        if (touching && scrollY > 0) {
                            setSelectionInner(mCurrPos + 1, false);
                            mCurrView.setEventEnabled(false);
                            mSlideOutView.setScrollY(scrollY);  //必然不为null, mCurrView变过去的
                            mCurrView.setScrollY(0);
                            mSlideOutViewDownScrollY = scrollY + target;
                            mCurrViewDownScrollY = 0 + target;
                        } else {
                            if (scrollY >= mHeight) {
                                setSelectionInner(mCurrPos + 1, false);
                                mCurrView.setEventEnabled(false);
                                scrollY -= mHeight;
                                mSlideOutView.setScrollY(mHeight);  //必然不为null, mCurrView变过去的
                                mCurrView.setScrollY(scrollY);
                                mSlideOutViewDownScrollY = mHeight + target;
                                mCurrViewDownScrollY = scrollY + target;
                            } else {
                                if (mCurrPos == 0 && scrollY < 0) {
                                    scrollY = (int) (scrollY * mStickyFirstLast);
                                    if (mStartSlideAtFirst && !beforeFirst) beforeFirst = true;
                                }
                                mCurrView.setScrollY(scrollY);
                            }
                        }
                    }
                }
            } else {
                if (mSlideOutView != null) {
                    int outViewScrollY = mSlideOutViewDownScrollY - target;
                    if (DEBUG) Log.d(TAG, "onScrollToY----outViewScrollY:" + outViewScrollY);
                    if (outViewScrollY >= -mHeight) {
                        if (afterLast && touching && mCurrView.getScrollY() >= 0) afterLast = false;
                        if (outViewScrollY <= 0) {
                            mSlideOutView.setScrollY(outViewScrollY);
                            if (mCurrView.getScrollY() != 0) mCurrView.setScrollY(0);
                        } else {
                            setSelectionInner(mCurrPos - 1, false);
                            mCurrView.setEventEnabled(false);
                            if (afterLast) afterLast = false;
                            //mSlideOutView已经改变
                            if (mSlideOutView != null) {
                                outViewScrollY -= mHeight; //把新的拉上来
                                mSlideOutView.setScrollY(outViewScrollY);
                                mSlideOutViewDownScrollY = outViewScrollY + target; //呼应前面的int outViewScrollY = mSlideOutViewDownScrollY - target;
                            }
                            mCurrView.setScrollY(0);
                            mCurrViewDownScrollY = 0 + target;
                        }
                    } else {
                        if (mCurrPos == mItemCount - 1) {
                            mSlideOutView.setScrollY(-mHeight);
                            outViewScrollY = (int) ((outViewScrollY + mHeight) * mStickyFirstLast);
                            mCurrView.setScrollY(outViewScrollY);
                            if (mStartSlideAtLast && !afterLast) afterLast = true;
                        } else {
                            setSelectionInner(mCurrPos + 1, false);
                            mCurrView.setEventEnabled(false);
                            outViewScrollY += mHeight;
                            mSlideOutView.setScrollY(outViewScrollY);  //必然不为null, mCurrView变过去的
                            mCurrView.setScrollY(0);
                            mSlideOutViewDownScrollY = outViewScrollY + target;
                            mCurrViewDownScrollY = 0 + target;
                        }
                    }
                } else if (mCurrView != null) {
                    int scrollY = mCurrViewDownScrollY - target;
                    if (DEBUGx) Log.d(TAG, "onScrollToY----scrollY:" + scrollY);
                    if (mCurrPos == mItemCount - 1) {   //说明只有一个(mItemCount == 1)，因为mSlideOutView == null, 又是最最后一个
                        if (scrollY < 0) {
                            if (mStartSlideAtLast && !afterLast) afterLast = true;
                            if (beforeFirst) beforeFirst = false;
                        } else if (scrollY > 0) {
                            if (mStartSlideAtFirst && !beforeFirst) beforeFirst = true;
                            if (afterLast) afterLast = false;
                        }
                        scrollY = (int) (scrollY * mStickyFirstLast);
                        mCurrView.setScrollY(scrollY);
                    } else {
                        if (beforeFirst && touching && scrollY <= 0) beforeFirst = false;
                        if (touching && scrollY < 0) {
                            setSelectionInner(mCurrPos + 1, false);
                            mCurrView.setEventEnabled(false);
                            mSlideOutView.setScrollY(scrollY);  //必然不为null, mCurrView变过去的
                            mCurrView.setScrollY(0);
                            mSlideOutViewDownScrollY = scrollY + target;
                            mCurrViewDownScrollY = 0 + target;
                        } else {
                            if (scrollY <= -mHeight) {
                                setSelectionInner(mCurrPos + 1, false);
                                mCurrView.setEventEnabled(false);
                                scrollY -= mHeight;
                                mSlideOutView.setScrollY(-mHeight);  //必然不为null, mCurrView变过去的
                                mCurrView.setScrollY(scrollY);
                                mSlideOutViewDownScrollY = -mHeight + target;
                                mCurrViewDownScrollY = scrollY + target;
                            } else {
                                if (mCurrPos == 0 && scrollY > 0) {
                                    scrollY = (int) (scrollY * mStickyFirstLast);
                                    if (mStartSlideAtFirst && !beforeFirst) beforeFirst = true;
                                }
                                mCurrView.setScrollY(scrollY);
                            }
                        }
                    }
                }
            }
            doSlideOrTransition(true);
        }

        @Override
        public void onScrollFinishedX() {}

        @Override
        public void onScrollFinishedY() {
            if (DEBUG) Log.e(TAG, "onScrollFinishedY----");
            final int height = getHeightWithDirection();
            if (mSlideOutView != null) {
                if (mSlideOutView.getScrollY() == 0) {
                    setSelectionInner(mCurrPos - 1, false);
                    //mCurrView.setEventEnabled(false);
                    if (mSlideOutView != null) mSlideOutView.setScrollY(height);
                    mCurrView.setScrollY(0);
                } else {
                    mSlideOutView.setScrollY(height);
                    mCurrView.setScrollY(0);
                }
            } else if (mCurrView != null) {
                if (mCurrView.getScrollY() == height) {
                    setSelectionInner(mCurrPos + 1, false);
                    //mCurrView.setEventEnabled(false);
                    if (mSlideOutView != null) mSlideOutView.setScrollY(height);
                    mCurrView.setScrollY(0);
                } else {
                    mCurrView.setScrollY(0);
                }
            }
            if (mCurrView != null && !mCurrView.isEventEnabled()) mCurrView.setEventEnabled(true);
            doSlideOrTransition(true);
            setScrollY(0);
            if (beforeFirst) {
                onSlideBeforeFirst();
            } else if (afterLast) {
                onSlideAfterLast();
            }
            beforeFirst = false;
            afterLast = false;
            mSliding = false;
        }
    };

    private int getTargetScrollY(View view, boolean upTrueDownFalse, float velocity, boolean sysDefFling) {
        final int scrollY = view.getScrollY();
        final int target;
        if (mSlideOutDirectionUp) {
            if (sysDefFling) {  //抛出的手势
                if (velocity >= 0) {    //向下
                    if (scrollY < mHeight - mDp48) {
                        target = 0;
                    } else {
                        target = mHeight;
                    }
                } else {    //向上
                    if (scrollY <= mDp48) {
                        target = 0;
                    } else {
                        target = mHeight;
                    }
                }
            } else {
                if (upTrueDownFalse) {
                    if (scrollY > mHeight / 3) {
                        target = mHeight;
                    } else {
                        target = 0;
                    }
                } else {
                    if (scrollY < mHeight * 2 / 3) {
                        target = 0;
                    } else {
                        target = mHeight;
                    }
                }
            }
        } else {
            if (sysDefFling) {  //抛出的手势
                if (velocity <= 0) {    //向上
                    if (scrollY > -mHeight + mDp48) {
                        target = 0;
                    } else {
                        target = -mHeight;
                    }
                } else {    //向下
                    if (scrollY >= -mDp48) {
                        target = 0;
                    } else {
                        target = -mHeight;
                    }
                }
            } else {
                if (upTrueDownFalse) {
                    if (scrollY < -mHeight * 2 / 3) {
                        target = -mHeight;
                    } else {
                        target = 0;
                    }
                } else {
                    if (scrollY > -mHeight / 3) {
                        target = 0;
                    } else {
                        target = -mHeight;
                    }
                }
            }
        }
        return target;
    }

    @Override
    public void onRequestInvalidate() {
        if (mSlideOutView != null) {
            mSlideOutView.invalidate();
        } else if (mCurrView != null) {
            mCurrView.invalidate();
        }
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        mSEvTracker.onComputeScroll();
        mScrollCalled = true;
    };

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        ensureScrollCalled();
        if (mScrollCalled) mScrollCalled = false;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        ensureScrollCalled();
        if (mScrollCalled) mScrollCalled = false;
    }

    private void ensureScrollCalled() {
        if (!mScrollCalled) {
            mSEvTracker.onComputeScroll();
            mScrollCalled = true;
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Result result = mSEvTracker.preDispatchTouchEvent(ev);
        return result.performSuper ? mSEvTracker.postDispatchTouchEvent(ev, super.dispatchTouchEvent(result.superEvent)) : result.consume();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final Result result = mSEvTracker.preOnInterceptTouchEvent(ev);
        return result.performSuper ? mSEvTracker.postOnInterceptTouchEvent(ev, super.onInterceptTouchEvent(result.superEvent)) : result.consume();
    }

    @Override
    @SuppressLint("ClickableViewAccessibility")
    public boolean onTouchEvent(MotionEvent ev) {
        final Result result = mSEvTracker.preOnTouchEvent(ev);
        return result.performSuper ? mSEvTracker.postOnTouchEvent(ev, super.onTouchEvent(result.superEvent)) : result.consume();
    }

    public void setSlideOutDirectionUp(boolean upTrueDownFalse) {
        mSlideOutDirectionUp = upTrueDownFalse;
    }

    private int getHeightWithDirection() {
        return mHeight * (mSlideOutDirectionUp ? 1 : -1);
    }

    @SuppressWarnings("unchecked")
    public <T extends AbsVSlideAdapter<?>> T getAdapter() {
        return (T)mAdapter;
    }

    public <T extends AbsVSlideAdapter<?>> void setAdapter(T adapter) {
        if (mAdapter != adapter) {
            unregisterAdapterObserver();
            removeAllViewsInLayout();
            if (DEBUGx) Log.d(TAG, "++++++++setAdapter----clear++++++++");
            mViewScraps.clear();
            mSlideOutView = null;
            mCurrView = null;
            mAdapter = adapter;
            mActiveViewsUpdated = false;
            updateCount();
            mPrevPos = -ACTIVE_COUNT;
            mCurrPos = 0;
            registerAdapterObserver();
            postUpdateActiveViews(false);
        }
    }

    private void updateCount() {
        if (mAdapter != null) {
            mItemCount = mAdapter.getCount();
            mViewScraps.updateViewTypeCount();
        } else {
            mItemCount = 0;
            mViewScraps.clear();
        }
        if(DEBUGx) Log.d(TAG, "[updateCount]mItemCount:" + mItemCount);
    }

    private void registerAdapterObserver() {
        if (mAdapter != null) {
            if (!mRegisted) {
                mAdapter.registerDataSetObserver(mDataSetObserver);
                mRegisted = true;
            }
        }
    }

    private void unregisterAdapterObserver() {
        if (mAdapter != null) {
            if (mRegisted) {
                mAdapter.unregisterDataSetObserver(mDataSetObserver);
                mRegisted = false;
            }
        }
    }

    @Override
    protected void onAttachedToWindow() {
        if (DEBUGx) Log.d(TAG, "onAttachedToWindow");
        registerAdapterObserver();
        adjustCurrPos();
        postUpdateActiveViews(false);
        super.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        if (DEBUGx) Log.d(TAG, "onDetachedFromWindow");
        unregisterAdapterObserver();
        removeAllViewsInLayout();
        if (DEBUG) Log.e(TAG, "++++++++onDetachedFromWindow----clear++++++++");
        mViewScraps.clear();
        mSlideOutView = null;
        mCurrView = null;
        mItemCount = 0;
        super.onDetachedFromWindow();
    }

    private final DataSetObserver mDataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            if (DEBUGx) Log.d(TAG, "onChanged");
            mActiveViewsUpdated = false;
            updateCount();
            adjustCurrPos();
            postUpdateActiveViews(true);
        };
        @Override
        public void onInvalidated() {
            if (DEBUGx) Log.d(TAG, "onInvalidated");
            removeAllViewsInLayout();
            if (DEBUG) Log.e(TAG, "++++++++onInvalidated----clear++++++++");
            mViewScraps.clear();
            mSlideOutView = null;
            mCurrView = null;
            mActiveViewsUpdated = false;
            updateCount();
            mPrevPos = -ACTIVE_COUNT;
            mCurrPos = 0;
            postUpdateActiveViews(false);
        };
    };

    public View getSelectedView() {
        return mCurrView == null ? null : mCurrView.getChildAt(0);
    }

    public int getSelectedPosition() {
        return mCurrPos;
    }

    public void setSelection(int position) {
        setSelectionInner(position, true);
    }

    private void setSelectionInner(int position, boolean post) {
        if (mCurrPos != position) {
            mPrevPos = mCurrPos;
            mCurrPos = position;
            adjustCurrPos();
            if (post) {
                postUpdateActiveViews(false);
            } else {
                //不要直接调用updateActiveViews(false), 可能前面还有refresh状态未处理
                mUpdateViewRun.run();
            }
        }
    }

    private void adjustCurrPos() {
        mCurrPos = Math.max(0, Math.min(mCurrPos, mItemCount - 1));
    }

    public void setAnimDuration(int duration) {
        mAnimDuration = duration;
    }

    public void setStickyFirstLast(float stickyFirstLast) {
        mStickyFirstLast = stickyFirstLast;
    }

    private void postUpdateActiveViews(boolean refresh) {
        mUpdateViewRun.setRefresh(refresh);
        post(mUpdateViewRun);
    }

    private final UpdateViewRunnable mUpdateViewRun = new UpdateViewRunnable();
    private class UpdateViewRunnable implements Runnable {
        private boolean mRefresh;
        public void setRefresh(boolean refresh) {
            mRefresh |= refresh;
        }
        @Override
        public void run() {
            updateActiveViews(mRefresh);
            /* 消息队列中的本消息，在执行前所设置的状态都在这一次处理了，后面的都可以不再执行。如果是在本消息之后设置的，那么还没开始设置呢（没走到那个消息）
             * 但是如果在上一句中触发了mOnSelectionListener.onSelection(mCurrPos)导致同步触发mDataSetObserver.onChanged(),
             * 进行了post, 而在下面这句又remove了，会导致mActiveViewsUpdated始终为false, 进而导致无法再滑动。
             */
            removeCallbacks(mUpdateViewRun);
            mRefresh = false;
        }
    };

    public void setEmptyView(View view) {
        mEmptyView = view;
    }

    public View getEmptyView() {
        return mEmptyView;
    }

    private void updateActiveViews(boolean refresh) {
        if (DEBUGx) Log.e(TAG, "updateActiveViews(" + refresh + "), mItemCount:" + mItemCount
                + ", mCurrPos:" + mCurrPos + ", mPrevPos:" + mPrevPos + ", mAdapter:" + mAdapter);
        //mSEvTracker.abortScrollY();   //不用中断，可能在拖动过程中也需要调整层级
        try {
            clearFocus();   //避免后面removeAllViewsInLayout()导致异常
        } catch (Throwable e) {
            if (DEBUGx) Log.e(TAG, "clearFocus", e);
        }
        if (mAdapter == null || mItemCount == 0) {
            removeAllViews();   //不用removeAllViewsInLayout()
            mViewScraps.scrapActiveViews();
            mCurrView = null;
            mSlideOutView = null;
            if(mEmptyView != null) {
                addViewInLayout(mEmptyView, -1, adjustLayoutParams(mEmptyView));
                mEmptyView.requestLayout();
                mEmptyView.invalidate();
            }
        } else if (refresh || mCurrPos != mPrevPos) {
            if(mEmptyView != null && mEmptyView.getParent() == this) {
                removeViewInLayout(mEmptyView);
            }
            final int offset = mCurrPos - mPrevPos;
            if (Math.abs(offset) >= ACTIVE_COUNT) {   //全部都要替换
                removeAllViewsInLayout();
                mViewScraps.scrapActiveViews();
                final View[] children = mViewScraps.fillActiveViews(mCurrPos - 1);
                for (View view : children) {
                    if (view != null) addViewInLayout(view, 0, adjustLayoutParams(view)); //children层次从上到下，adapter position递增，children index递减
                }
            } else {    //只替换部分
                final List<ScrollableFrameLayout> scraps = mViewScraps.offsetActiveViews(offset);
                for (View view : scraps) {
                    if (DEBUG) Log.i(TAG, "updateActiveViews----3----offsetActiveViews----remove:" + getLog(view));
                    removeViewInLayout(view);
                }
                final List<ScrollableFrameLayout> outScraps = mViewScraps.removeOutOfCountBoundsViews(mCurrPos - 1);
                for (View view : outScraps) {
                    if (DEBUG) Log.i(TAG, "updateActiveViews----4----removeOutOfCountBoundsViews----remove:" + getLog(view));
                    removeViewInLayout(view);
                }
                final View[] newActives = mViewScraps.fillActiveViews(mCurrPos - 1);
                if (offset >= 0) {   //删除表面的View, 并在底部添加
                    for (View view : newActives) {
                        if (DEBUG) Log.i(TAG, "updateActiveViews----5----fillActiveViews----offset>=0----add:" + getLog(view));
                        if (view != null) addViewInLayout(view, 0, adjustLayoutParams(view));
                    }
                } else {   //要删除底部的View, 在表面叠加
                    for (int i = newActives.length - 1; i >= 0; i--) {
                        final View view = newActives[i];
                        if (DEBUG) Log.i(TAG, "updateActiveViews----6----fillActiveViews----offset<0----add:" + getLog(view));
                        if (view != null) addViewInLayout(view, -1, adjustLayoutParams(view));
                    }
                }
                if (refresh) mViewScraps.refreshChildren(mCurrPos - 1, Arrays.asList(newActives));
            }
            ensureLayoutAndSlideViews();
            if (mCurrPos != mPrevPos) mPrevPos = mCurrPos;
            if (!mSliding) doSlideOrTransition(false);
            mActiveViewsUpdated = true;
            onSelection();
            if (DEBUGx) Log.i(TAG, "updateActiveViews----8----mViewScraps:" + mViewScraps);
        }
    }

    private void ensureLayoutAndSlideViews() {
        mCurrView = null;
        mSlideOutView = null;
        final int childCount = getChildCount();
        if (DEBUG) Log.e(TAG, "ensureLayoutAndSlideViews----7----childCount:" + childCount);
        for (int i = 0; i < childCount; i++) {
            final ScrollableFrameLayout child = (ScrollableFrameLayout)getChildAt(i);
            if (mCurrPos > 0 && childCount > 1 && i == childCount - 1) {
                mSlideOutView = child;
                if (!mSliding) mSlideOutView.setScrollY(getHeightWithDirection());
            } else {
                if (childCount == 1 || mCurrPos == 0 && i == childCount - 1
                        || mCurrPos == mItemCount - 1 && i == 0
                        || mCurrPos > 0 && childCount > 1 && i == childCount - 2) {
                    mCurrView = child;
                    if (!mSliding) mCurrView.setScrollY(0);
                } else {
                    child.setScrollY(0);
                }
            }
            child.setEventEnabled(child == mCurrView);
            final View item = ((FrameLayout)child).getChildAt(0);
            child.requestLayout();
            child.invalidate();
            item.requestLayout();
            item.invalidate();
        }
    }

    private LayoutParams adjustLayoutParams(View view) {
        LayoutParams lp = (LayoutParams)view.getLayoutParams();
        if (lp == null) {
            lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            view.setLayoutParams(lp);
        } else {
            if (lp.width != LayoutParams.MATCH_PARENT) {
                lp.width = LayoutParams.MATCH_PARENT;
            }
            if (lp.height != LayoutParams.MATCH_PARENT) {
                lp.height = LayoutParams.MATCH_PARENT;
            }
        }
        return lp;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mHeight = getHeight();
        if (!mSliding && mSlideOutView != null) mSlideOutView.setScrollY(getHeightWithDirection());
    }

    public View[] getItemViews() {
        return mViewScraps.getItemViews();
    }

    protected static String getLog(View view) {
        return view == null ? null : view.getClass().getSimpleName() + "@" + view.hashCode();
    }

    protected void doTransition(float percent, View[] cards, int slideCardIndex, int currentShowIndex) {}

    private void onSelection() {
        post(mOnSelectionRun);
    }

    private void doSlideOrTransition(boolean sliding) {
        final float percent;
        final int slideCardIndex;
        if (mSlideOutView != null) {
            percent = mSlideOutView.getScrollY() * 1f / mHeight;
            slideCardIndex = 0;
            //currShowIndex = 1;
        } else {
            percent = mCurrView.getScrollY() * 1f / mHeight;
            slideCardIndex = 1;
        }
        doTransition(percent, getItemViews(), slideCardIndex, 1);
        if (sliding && mOnSelectionListener != null) mOnSelectionListener.onSliding(percent);
    }

    private void onSlideBeforeFirst() {
        post(mOnSlideBeforeFirstRun);
    }

    private void onSlideAfterLast() {
        post(mOnSlideAfterLastRun);
    }

    private final Runnable mOnSelectionRun = new Runnable() {
        @Override
        public void run() {
            if (mOnSelectionListener != null) mOnSelectionListener.onSelection(mCurrPos);
        }
    };

    private final Runnable mOnSlideBeforeFirstRun = new Runnable() {
        @Override
        public void run() {
            if (mOnSelectionListener != null) mOnSelectionListener.onSlideBeforeFirst();
        }
    };

    private final Runnable mOnSlideAfterLastRun = new Runnable() {
        @Override
        public void run() {
            if (mOnSelectionListener != null) mOnSelectionListener.onSlideAfterLast();
        }
    };

    public void setOnSelectionListener(OnSelectionListener l) {
        mOnSelectionListener = l;
    }

    public static interface OnSelectionListener {
        void onSelection(int position);
        void onSliding(float percent);
        void onSlideBeforeFirst();
        void onSlideAfterLast();
    }

    private static class ScrollableFrameLayout extends FrameLayout {
        private boolean mEventEnabled;
        public ScrollableFrameLayout(Context context) {
            super(context);
        }

        public void setEventEnabled(boolean enabled) {
            if (mEventEnabled != enabled) mEventEnabled = enabled;
        }

        public boolean isEventEnabled() {
            return mEventEnabled;
        }

        @Override
        public boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params) {
            return super.addViewInLayout(child, index, params);
        }

        @Override
        public boolean dispatchTouchEvent(MotionEvent e) {
            if (mEventEnabled) {
                return super.dispatchTouchEvent(e);
            } else {
                return false;
            }
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (mEventEnabled) {
                return super.dispatchKeyEvent(event);
            } else {
                return false;
            }
        }
    }

    private static class Cell {
        private final View mView;
        private final int mType;

        public Cell(View view, int type) {
            mType = type;
            mView = view;
        }
    }

    private class RecycleBin {
        /**用于解决setScrollY()无法滚动背景的问题，给所有的items加一层parent.**/
        private final ScrollableFrameLayout[] mItemParents = new ScrollableFrameLayout[ACTIVE_COUNT];
        /**存储当前要显示的items的View和相关的type. 数组的index顺序与item的position顺序同步。注意本数组与{@link #mItemParents}也保持同步。**/
        private final Cell[] mActiveViews = new Cell[ACTIVE_COUNT];
        private LinkedList<View>[] mScrapViews;

        public RecycleBin() {
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                mItemParents[i] = new ScrollableFrameLayout(getContext());
            }
        }

        private void updateViewTypeCount() {
            final int viewTypeCount = mAdapter.getViewTypeCount();
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            } else if (mScrapViews == null || mScrapViews.length < viewTypeCount) {
                @SuppressWarnings("unchecked")
                final LinkedList<View>[] scrapViews = new LinkedList[viewTypeCount];
                for (int i = 0; i < viewTypeCount; i++) {
                    scrapViews[i] = new LinkedList<View>();
                }
                mScrapViews = scrapViews;
            }
        }

        private void scrapActiveViews() {
            for (int i = 0; i < mActiveViews.length; i++) {
                final Cell cell = mActiveViews[i];
                if (cell != null) {
                    mActiveViews[i] = null;
                    mItemParents[i].removeAllViewsInLayout();
                    addScrapView(cell.mView, cell.mType);
                }
            }
        }

        private List<ScrollableFrameLayout> offsetActiveViews(int offset) {
            final List<ScrollableFrameLayout> scraps = new LinkedList<ScrollableFrameLayout>();
            if (offset != 0) {
                final Cell[] actives = new Cell[ACTIVE_COUNT];
                System.arraycopy(mActiveViews, 0, actives, 0, ACTIVE_COUNT);
                final ScrollableFrameLayout[] itemParents = new ScrollableFrameLayout[ACTIVE_COUNT];
                System.arraycopy(mItemParents, 0, itemParents, 0, ACTIVE_COUNT);

                for (int i = 0; i < ACTIVE_COUNT; i++) {
                    final Cell cell = actives[i];
                    final ScrollableFrameLayout itemParent = itemParents[i];
                    int k = i - offset;
                    if (k < 0 || k > ACTIVE_COUNT - 1) {
                        k = k < 0 ? k + ACTIVE_COUNT : k - ACTIVE_COUNT;
                        mActiveViews[k] = null; //置为空
                        mItemParents[k] = itemParent;
                        if (cell != null) {
                            itemParent.removeAllViewsInLayout();
                            scraps.add(itemParent);
                            addScrapView(cell.mView, cell.mType);
                        }
                    } else {
                        mActiveViews[k] = cell;
                        mItemParents[k] = itemParent;
                    }
                }
            }
            return scraps;
        }

        private List<ScrollableFrameLayout> removeOutOfCountBoundsViews(int startPosition) {
            final List<ScrollableFrameLayout> scraps = new LinkedList<ScrollableFrameLayout>();
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                final Cell cell = mActiveViews[i];
                if (cell != null) {
                    final int position = startPosition + i;
                    if (position >= 0 && position < mItemCount) {
                        //nothing...
                    } else {
                        mActiveViews[i] = null; //置为空
                        final ScrollableFrameLayout itemParent = mItemParents[i];
                        itemParent.removeAllViewsInLayout();
                        scraps.add(itemParent);
                        addScrapView(cell.mView, cell.mType);
                    }
                }
            }
            return scraps;
        }

        private View[] fillActiveViews(int startPosition) {
            final ScrollableFrameLayout[] actives = new ScrollableFrameLayout[ACTIVE_COUNT];
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                final Cell cell = mActiveViews[i];
                if (cell == null) {
                    final int position = startPosition + i;
                    if (position >= 0 && position < mItemCount) {
                        final ScrollableFrameLayout itemParent = mItemParents[i];
                        final View newView = mAdapter.getView(position, getScrapView(position), itemParent);
                        mActiveViews[i] = new Cell(newView, getTypeWithCheckState(position));
                        itemParent.removeAllViewsInLayout();
                        itemParent.addViewInLayout(newView, 0, adjustLayoutParams(newView));
                        actives[i] = itemParent;
                    }
                } else {
                    final View oldView = mItemParents[i].getChildAt(0);
                    if (DEBUGx) Log.d(TAG, "fillActiveViews----oldView:" + getLog(oldView) + ", cell.mView:" + getLog(cell.mView));
                    if (oldView != cell.mView) throw new IllegalStateException("oldView != cell.mView");   //断言
                }
            }
            return actives;
        }

        private View[] getItemViews() {
            //还是每次new吧，不然回收逻辑较复杂
            final View[] items = new View[ACTIVE_COUNT];
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                final Cell cell = mActiveViews[i];
                if (cell != null) {
                    items[i] = cell.mView;
                }
            }
            return items;
        }

        private void refreshChildren(int startPosition, List<View> excepts) {
            if (DEBUGx) Log.i(TAG, "refreshChildren----");
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                Cell cell = mActiveViews[i];
                if (cell != null) {
                    final int position = startPosition + i;
                    final ScrollableFrameLayout itemParent = mItemParents[i];
                    View oldView = itemParent.getChildAt(0);
                    if (DEBUGx) Log.d(TAG, "refreshChildren----oldView:" + getLog(oldView) + ", cell.mView:" + getLog(cell.mView));
                    if (oldView != cell.mView) throw new IllegalStateException("oldView != cell.mView");   //断言
                    final int type = getTypeWithCheckState(position);
                    if (DEBUGx) Log.d(TAG, "refreshChildren----cell.mType:" + cell.mType + ", type:" + type);
                    if (cell.mType == type) {
                        final View newView = mAdapter.getView(position, oldView, itemParent);
                        if (newView != oldView) {
                            mActiveViews[i] = new Cell(newView, type);
                            itemParent.removeAllViewsInLayout();
                            itemParent.addViewInLayout(newView, 0, adjustLayoutParams(newView));
                            addScrapView(cell.mView, cell.mType);
                        }
                    } else {
                        oldView = getScrapView(position);
                        if (DEBUGx) Log.d(TAG, "refreshChildren----cell.mType != type----oldView:" + getLog(oldView));
                        final View newView = mAdapter.getView(position, oldView, itemParent);
                        if (newView != oldView) addScrapView(oldView, type);
                        mActiveViews[i] = new Cell(newView, type);
                        itemParent.removeAllViewsInLayout();
                        itemParent.addViewInLayout(newView, 0, adjustLayoutParams(newView));
                        addScrapView(cell.mView, cell.mType);
                    }
                }
            }
        }

        private View getScrapView(int position) {
            LinkedList<View> list = mScrapViews[getTypeWithCheckState(position)];
            View view = list.size() == 0 ? null : list.remove(0);
            if (DEBUG) Log.i(TAG, "getScrapView----position:" + position + ", view:" + getLog(view));
            return view;
        }

        private void addScrapView(View scrap, int type) {
            mScrapViews[type].add(scrap);
        }

        private int getTypeWithCheckState(int position) {
            final int type = mAdapter.getItemViewType(position);
            if (type >= 0 && type < mScrapViews.length) {
                return type;
            } else {
                throw new IllegalStateException("[状态不对]position:" + position + ", viewTypeCount:" + mScrapViews.length + ", itemViewType:" + type);
            }
        }

        private void clear() {
            if (DEBUG) Log.e(TAG, "++++++++clear++++++++");
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                final ScrollableFrameLayout itemParent = mItemParents[i];
                if (itemParent != null) {
                    itemParent.removeAllViewsInLayout();
                }
            }
            for (int i = 0; i < ACTIVE_COUNT; i++) {
                final Cell cell = mActiveViews[i];
                if (cell != null) {
                    mActiveViews[i] = null;
                }
            }
            if (mScrapViews != null) {
                for (LinkedList<View> list : mScrapViews) {
                    list.clear();
                }
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("\n");
            sb.append("mItemParents:\n");
            for (View view : mItemParents) {
                sb.append(getLog(view) + "\n");
            }
            sb.append("mActiveViews:\n");
            for (Cell cell : mActiveViews) {
                sb.append("[" + (cell == null ? "null" : "type:" + cell.mType + ", " + getLog(cell.mView)) + "]\n");
            }
            sb.append("mScrapViews:\n");
            for (int i = 0; i < mScrapViews.length; i++) {
                List<View> list = mScrapViews[i];
                sb.append("\ttype:" + i + ":\n");
                for (View view : list) {
                    sb.append(getLog(view) + "\n");
                }
            }
            return sb.toString();
        }
    }

    public static abstract class AbsVSlideAdapter<T> implements Adapter {
        public final List<T> EMPTY = Collections.emptyList();
        private List<T> mData;
        private LayoutInflater mInflater;

        public AbsVSlideAdapter(Context context) {
            this(context, null);
        }

        public AbsVSlideAdapter(Context context, List<T> data) {
            mInflater = LayoutInflater.from(context);
            mData = data == null ? EMPTY : data;
        }

        public void setDataSource(List<T> data) {
            mData = data == null ? EMPTY : data;
            notifyDataSetChanged();
        }

        public List<T> getData() {
            return mData;
        }

        protected LayoutInflater getInflater() {
            return mInflater;
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public T getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        private final DataSetObservable mDataSetObservable = new DataSetObservable();

        public boolean hasStableIds() {
            return false;
        }

        public void registerDataSetObserver(DataSetObserver observer) {
            mDataSetObservable.registerObserver(observer);
        }

        public void unregisterDataSetObserver(DataSetObserver observer) {
            mDataSetObservable.unregisterObserver(observer);
        }

        public void notifyDataSetChanged() {
            mDataSetObservable.notifyChanged();
        }

        public void notifyDataSetInvalidated() {
            mDataSetObservable.notifyInvalidated();
        }

        @Override
        public int getItemViewType(int position) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public boolean isEmpty() {
            return getCount() == 0;
        }
    }
}
